package model.crafting;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import model.inventory.item.Item;



public class RecipeComparer {
	protected List<Recipe> recipes;
	private final String defaultRecipeLocation = "/model/crafting/recipes.xml";

	public RecipeComparer(){
		recipes = new LinkedList<Recipe>();
		loadRecipes(defaultRecipeLocation);
	}

	public RecipeComparer(String recipeLocation){
		recipes = new LinkedList<Recipe>();
		loadRecipes(recipeLocation);
	}

	private void loadRecipes(String recipeFile) {
		try {
			File file = new File(getClass().getResource(recipeFile).getPath());
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(file);
			doc.getDocumentElement().normalize();
			
			//Gets the parent node of each recipe
			NodeList recipeList = doc.getElementsByTagName("recipe");
			for (int s = 0; s < recipeList.getLength(); s++) {
				boolean validRecipe = true;
				//Gets the attributes of the recipe
				NamedNodeMap att = recipeList.item(s).getAttributes();
				Recipe recipe = new Recipe(Integer.parseInt(att.getNamedItem("width").getNodeValue()), Integer.parseInt(att.getNamedItem("height").getNodeValue()));
				
				//Gets all the input and output nodes in the recipe
				NodeList recipeNodes = recipeList.item(s).getChildNodes();
				for(int i = 0; i < recipeNodes.getLength(); i++){
					Node item = recipeNodes.item(i);
					
					if(item.getNodeName() == "output" || item.getNodeName() == "input"){
						int itemID = Integer.parseInt(item.getAttributes().getNamedItem("itemid").getNodeValue());
						int quantity = Integer.parseInt(item.getAttributes().getNamedItem("quantity").getNodeValue());
						
						if(item.getNodeName() == "output"){
							//Adds the output item to the recipe
							validRecipe = recipe.setOutput(itemID, quantity);
						}else{
							int x = Integer.parseInt(item.getAttributes().getNamedItem("x").getNodeValue());
							int y = Integer.parseInt(item.getAttributes().getNamedItem("y").getNodeValue());
							//Adds an input item to the recipe
							validRecipe = recipe.setInput(x, y, itemID, quantity);
						}
					}
				}
				if(validRecipe)
					recipes.add(recipe);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Item compare(CraftingTable ct){
		for(Recipe r : recipes){
			if(ct.HEIGHT >= r.HEIGHT && ct.WIDTH >= r.WIDTH){
				if(r.equals(ct)){
					return r.outSlot;
				}
			}
		}
		return null;
	}

	protected class Recipe extends CraftingTable{

		public Recipe(int w, int h) {
			super(w, h);
		}

		public boolean setOutput(int itemID, int quantity){
			if(quantity > 0){
				outSlot = new Item(itemID, quantity);
				return true;
			}
			return false;
		}

		public boolean setInput(int x, int y, int itemID, int quantity){
			if(x < this.WIDTH && x >= 0 && y < this.HEIGHT && y >= 0 && quantity > 0){
				slots[x][y] = new Item(itemID, quantity);
				return true;
			}
			return false;
		}
		
		public int getItemID(int x, int y){
			if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT)
				return -1;
			if(this.slots[x][y] == null)
				return -1;
			return this.slots[x][y].getID();
		}

		/**boolean equals(CraftingTable)
		 * Checks the craftingTable to see if it fulfills this (Recipe)
		 * 
		 * @param ct
		 * @return true - table == recipe; false - table != recipe
		 */
		public boolean equals(CraftingTable ct){
			//Fast check if the crafting table is too small for this recipe
			if(ct.WIDTH < this.WIDTH || ct.HEIGHT < this.HEIGHT)
				return false;
			
			//Find the first item in the recipe (used as an anchor point)
			int firstRItemX = -1, firstRItemY = -1;
			int firstTItemX = -1, firstTItemY = -1;
			endRecipe:
			for(int y = 0; y < this.HEIGHT; y++){
				for(int x = 0; x < this.WIDTH; x++){
					if(this.slots[x][y] != null){
						firstRItemX = x; firstRItemY = y;
						break endRecipe;
					}
				}
			}
			//Fast check if Recipe does not contain any ingredients
			if(firstRItemX == -1 || firstRItemY == -1)
				return false;

			//Find the first item in the crafting table (used as an anchor point)
			endTable:
			for(int y = 0; y < ct.HEIGHT; y++){
				for(int x = 0; x < ct.WIDTH; x++){
					if(ct.slots[x][y] != null){
						firstTItemX = x; firstTItemY = y;
						break endTable;
					}
				}
			}
			//Fast check if crafting table coes not contain any ingredients
			if(firstTItemX == -1 || firstTItemY == -1)
				return false;
			
			//Fast check if the first item in the crafting table does not match the Recipe
			if(!ct.slots[firstTItemX][firstTItemY].equals(this.slots[firstRItemX][firstRItemY]))
				return false;

			//The mapping from the crafting Table to the recipe
			int ctToRX = firstTItemX - firstRItemX;
			int ctToRY = firstTItemY - firstRItemY;
			
			//Checks that all slots to use in the recipe are within the range of the table
			if(ctToRX < 0 || ctToRY < 0 || ctToRX + this.WIDTH > ct.WIDTH || ctToRY + this.HEIGHT > ct.HEIGHT)
				return false;

			//Checks that all slots not in the range of the recipe are empty
			for(int y = 0; y < ct.HEIGHT; y++){
				for(int x = 0; x < ct.WIDTH; x++){
					if(x < ctToRX || y < ctToRY || x >= ctToRX + this.WIDTH || y >= ctToRY + this.HEIGHT){
						if(ct.slots[x][y] != null)
							return false;
					}
				}
			}

			//Checks that all items in the crafting table match the recipe
			for(int y = 0; y < this.HEIGHT; y++){
				for(int x = 0; x < this.WIDTH; x++){
					if(this.slots[x][y] == null || ct.slots[ctToRX + x][ctToRY + y] == null){
						if(this.slots[x][y] == null && ct.slots[ctToRX + x][ctToRY + y] != null)
							return false;
						if(this.slots[x][y] != null && ct.slots[ctToRX + x][ctToRY + y] == null)
							return false;
					}else if(!this.slots[x][y].equals(ct.slots[ctToRX + x][ctToRY + y]))
						return false;
				}
			}
			return true;
		}
	}

	public int numRecipes() {
		return recipes.size();
	}
}
